﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net.NetworkInformation;
using System.ComponentModel;
using System.Net;
using System.Threading;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace Pingy
{
	public class Pinger : INotifyPropertyChanged
	{
		BackgroundWorker pingWorker = new BackgroundWorker();
		Ping ping = new Ping();

		List<IPAddress> repliesReceived;

		public Pinger()
		{
			pingWorker.WorkerSupportsCancellation = true;
			pingWorker.DoWork += new DoWorkEventHandler(pinger_DoWork);
			pingWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(pinger_RunWorkerCompleted);
		}

		int pingTimeOut = 25;
		public int PingTimeOut
		{
			get { return pingTimeOut; }
			set { pingTimeOut = value; NotifyPropertyChanged("PingTimeOut"); }
		}

		bool isRunning = false;
		public bool IsRunning
		{
			get { return isRunning; }
			set { isRunning = value; NotifyPropertyChanged("IsRunning"); }
		}

		bool fetchHostInfo = false;
		public bool FetchHostInfo
		{
			get { return fetchHostInfo; }
			set { fetchHostInfo = value; NotifyPropertyChanged("FetchHostInfo"); }
		}

		public event EventHandler<PingEventArgs> ReplyReceived;
		public event EventHandler<PingEventArgs> PingSent;
		public event EventHandler<PingCompleteEventArgs> PingComplete;

		public void PingIPRange(IPRange[] ipAddresses)
		{
			if (ipAddresses == null)
				return;

			repliesReceived = new List<IPAddress>();
			pingWorker.RunWorkerAsync(ipAddresses);
		}

		public void PingHost(string host)
		{
			if (string.IsNullOrEmpty(host))
				return;

			repliesReceived = new List<IPAddress>();
			pingWorker.RunWorkerAsync(host);
		}

		public void Stop()
		{
			if (!IsRunning)
				return;

			pingWorker.CancelAsync();
		}

		void pinger_DoWork(object sender, DoWorkEventArgs e)
		{
			IsRunning = true;

			DateTime start = DateTime.Now;
			bool cancelled = false;

			if (e.Argument is IPRange[])
			{
				IPRange[] ipAddresses = e.Argument as IPRange[];

				foreach (IPRange range in ipAddresses)
				{
					foreach (IPAddress address in range)
					{
						if (pingWorker.CancellationPending)
						{
							ping.SendAsyncCancel();
							cancelled = true;
							return;
						}

						PingCommand command = new PingCommand(address);

						SendPing(command, address.ToString(), FetchHostInfo);
					}
				}
			}
			else if (e.Argument is string)
			{
				string hostName = e.Argument as string;

				PingCommand command = new PingCommand(hostName);

				SendPing(command, hostName, false);
			}

			DateTime end = DateTime.Now;
			TimeSpan elapsed = end.Subtract(start);

			if (PingComplete != null)
				PingComplete(this, new PingCompleteEventArgs(elapsed, repliesReceived, cancelled));
		}

		private void SendPing(PingCommand pingCommand, string hostnameOrAddress, bool fetchHostInfo)
		{
			if (PingSent != null)
			{
				PingSent(this, new PingEventArgs(pingCommand));
			}

			try
			{
				pingCommand.Status = "Pinging...";
				PingReply reply = ping.Send(hostnameOrAddress, PingTimeOut);

				if (reply.Address != null)
				{
					pingCommand.Address = reply.Address;
				}

				pingCommand.Reply = reply;
				pingCommand.Status = reply.Status.ToString();

				if (reply.Status == IPStatus.Success)
				{
					repliesReceived.Add(reply.Address);

					if (fetchHostInfo)
					{
						GetHostInfo(pingCommand);
					}
				}

				if (ReplyReceived != null)
				{
					ReplyReceived(this, new PingEventArgs(pingCommand));
				}
			}
			catch (PingException ex)
			{
				pingCommand.Status = ex.GetBaseException().Message;
			}
		}

		void pinger_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			IsRunning = false;
		}

		private void GetHostInfo(PingCommand cmd)
		{
			cmd.HostInfo = "Looking for host...";

			Task.Factory.StartNew(() =>
			{
				var host = Network.GetHostInfo(cmd.Address.ToString());

				if (host != null)
				{
					cmd.HostInfo = host.HostName;
				}
				else
				{
					cmd.HostInfo = "Looking for host...";
				}
			});
		}

		#region INotifyPropertyChanged Members

		public void NotifyPropertyChanged(string propertyName)
		{
			if (PropertyChanged != null)
			{
				PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;

		#endregion
	}

	public class PingEventArgs : EventArgs
	{
		public PingCommand PingCommand;

		public PingEventArgs(PingCommand command)
		{
			PingCommand = command;
		}
	}

	public class PingCompleteEventArgs : EventArgs
	{
		public TimeSpan Elapsed;
		public List<IPAddress> AddressesFound;
		public bool IsCancelled;

		public PingCompleteEventArgs(TimeSpan elapsed, List<IPAddress> addressesFound, bool isCancelled)
		{
			Elapsed = elapsed;
			AddressesFound = addressesFound;
			IsCancelled = isCancelled;
		}
	}
}
